﻿/* Copyright (C) Siemens AG, 2012. All Rights Reserved.
 * Author: Roland Eckl, Corporate Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.ComponentModel;
using System.ServiceModel;

namespace RVM.Infrastructure.Communication.WCF.NetPipe
{
    /// <summary>
    /// Importing using WCF and net.pipe binding.
    /// This importer is always using the ProcessIdentificationProvider!
    /// </summary>
    public class NetPipeViewModelImporter : IViewModelImporter
    {
        #region Private Members

        /// <summary>
        /// If the connection is closed is explicitely closed by this class, 
        /// this flag ensures that the Faulted event is not thrown.
        /// If it was not explicitely closed, the closed event results in Faulted.
        /// </summary>
        private bool _closing = false;

        /// <summary>
        /// Indicates if importer is connected to some exporter.
        /// </summary>
        private bool _connected = false;

        /// <summary>
        /// Unique ID of the connected exporter.
        /// </summary>
        private string _id = string.Empty;

        /// <summary>
        /// Corresponding session object, which is shared among compatible importers and exporters.
        /// </summary>
        private Session _session = null;

        /// <summary>
        /// Proxy for the remote/exporting agent.
        /// </summary>
        private IViewModelAgent _pipeProxy = null;

        /// <summary>
        /// Service Channel.
        /// </summary>
        private IServiceChannel _channel = null;

        /// <summary>
        /// Local object which receives callbacks from the remote agent.
        /// </summary>
        private NetPipeViewModelCallback _pipeCallback = null;

        /// <summary>
        /// Factory for duplex connections.
        /// </summary>
        private DuplexChannelFactory<IViewModelAgent> _pipeFactory = null;

        #endregion

        #region Public Members

        /// <summary>
        /// Public accessor for the unique ID of the connected exporter.
        /// </summary>
        public string Id { get { return _id; } }

        /// <summary>
        /// Public accessor for the connection state.
        /// </summary>
        public bool Connected { get { return _connected; } }

        /// <summary>
        /// Public Accessor of the session's shared Id.
        /// </summary>
        public string SessionId
        {
            get
            {
                if (_session != null)
                    return _session.Id;
                return string.Empty;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="exporterId">Id of Exporter</param>
        /// <param name="sharedSession">Session identifying compatible exporters and importers</param>
        public NetPipeViewModelImporter(string exporterId, Session sharedSession)
        {
            _id = exporterId;
            _session = sharedSession;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Opens a net.pipe connection to an exporter.
        /// </summary>
        /// <returns>TRUE, if exporter is available and connection could be established.</returns>
        public bool Start()
        {
            string uri = "net.pipe://localhost/" + SessionId + "/" + _id + @"/ViewModelExporter";

            try
            {
                // TODO: Currently the connection is dropped when there some longer inactivity (-> ObjectDisposed exception on IServiceChannel)
                NetNamedPipeBinding binding = new NetNamedPipeBinding();
                binding.ReceiveTimeout = TimeSpan.MaxValue;

                _pipeCallback = new NetPipeViewModelCallback();
                _pipeFactory =
                    new DuplexChannelFactory<IViewModelAgent>(
                        _pipeCallback,
                        binding,
                        new EndpointAddress(
                            uri));
                _pipeFactory.Faulted += OnPipeFactoryFaulted;
                _pipeFactory.Closing += OnPipeFactoryClosing;
                _pipeFactory.Open();
                _pipeProxy = _pipeFactory.CreateChannel();
                _channel = ((IServiceChannel)_pipeProxy);
                _channel.Faulted += OnPipeFactoryFaulted;
                _channel.Open();
                _connected = true;
            }
            catch (Exception)
            {
                _connected = false;
                return false;
            }

            return _connected;
        }

        /// <summary>
        /// Disconnects the importer from an exporter. Therefore the net.pipe connection is closed.
        /// </summary>
        /// <returns></returns>
        public bool Stop()
        {
            // TODO: Unregister all ViewModels (so the exporters can explicitely remove the agent, instead of doing this on the next exception themselves)
            try
            {
                _closing = true;
                if (_channel != null)
                    _channel.Close();
                if (_pipeFactory != null)
                    _pipeFactory.Close();
                _pipeProxy = null;
                _pipeCallback = null;
            }
            catch (Exception)
            {
            }
            _connected = false;
            _id = string.Empty;
            _closing = false;
            return !_connected;
        }

        /// <summary>
        /// Retrieves an exported/remote ViewModel through the established net.pipe connection.
        /// </summary>
        /// <param name="t">Type of the ViewModel</param>
        /// <param name="id">Unique ID of the exported ViewModel</param>
        /// <returns></returns>
        public RemotableViewModel GetViewModel(Type t, string id)
        {
            RemotableViewModel viewmodel = null;

            // Create RVM if the requested viewmodel extends this class, otherwise create traditional VM and associated proxy with it.
            if (t.IsSubclassOf(typeof(RemotableViewModel)))
            {
                // TODO: Clean up parameters!
                object[] par = new object[] { /*false*/ };
                // Create a local viewmodel, inject the id, callback and agent.
                viewmodel = Activator.CreateInstance(t, par) as RemotableViewModel;
            }
            else
            {
                object[] par = new object[] { /*false*/ };
                INotifyPropertyChanged ordinary = Activator.CreateInstance(t, par) as INotifyPropertyChanged;
                viewmodel = new ViewModelProxy(ordinary);
            }

            try
            {
                if (viewmodel != null)
                {
                    _pipeProxy.Register(id);
                    viewmodel.InjectRemoteViewModelAgent(id, _pipeCallback, _pipeProxy, this);
                    //TODO: Keep reference on all registered viewmodels? (Cp. Stop())
                }
            }
            catch (Exception)
            {
            }

            return viewmodel;
        }

        /// <summary>
        /// Event to indicate that the connection to the exporter got lost.
        /// </summary>
        public event EventHandler Faulted;

        #endregion

        #region Private Methods

        /// <summary>
        /// Handler that is called when the factory closes.
        /// If it was not explicitely close, the Faulted event will be raised.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnPipeFactoryClosing(object sender, EventArgs e)
        {
            // No explicit closing through RVM framework... possibly some time-out? Faulted channel to signal there is something wrong.
            if(!_closing)
            {
                if (Faulted != null)
                    Faulted(this, new EventArgs());
            }
        }

        /// <summary>
        /// Handler that is called if the factory turns into faulted state. Raises the importer's Faulted event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnPipeFactoryFaulted(object sender, EventArgs e)
        {
            // If connection was not established yet, it did not fault but fail.
            if (_connected)
            {
                Stop();
                if (Faulted != null)
                    Faulted(this, new EventArgs());
            }
        }

        #endregion
    }
}
